package org.ala.tiktools.sharddb.mybatis.pager.with_order;

import org.ala.tiktools.mybatis.interfaces.AbstractOrderBy;
import org.ala.tiktools.mybatis.pager.Pagement;
import org.ala.tiktools.mybatis.pager.Pager;
import org.ala.tiktools.sharddb.mybatis.executor.ShardExecutor;
import org.ala.tiktools.sharddb.mybatis.executor.ShardNoTxExecutor;
import org.ala.tiktools.sharddb.mybatis.interceptor.Shard;
import org.ala.tiktools.sharddb.mybatis.pager.parameter.ShardPagerParameter;
import org.ala.tiktools.sharddb.mybatis.router.ShardInfo;
import org.ala.tiktools.tools.kv.KV;
import org.ala.tiktools.tools.kv.Maps;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 *  含OrderBy的分页
 *  1）先求汇总数量。以及每个分表的数量。本次分页在总数量中的区域范围[left, right]（left从1开始）
 *  2）记录游标i，从0开始
 *      表示当前已经确定要舍弃的前缀数据，在本轮查询中的下标，以及其排序值等于多少
 *  3）如果 i 到 left 的距离已经 < max(pageSize, shardSize)，表示游标已经滚到了left附近，可以一次取出
 *      每个分库分表查询 right - i 的数据量，聚合后取出 [left, right] 的区域
 *  4）假设数据搜均匀分布的，每个分表分摊 (left-1) / shardSize 的数据（limit = (left-1) / shardSize offset = 0）
 *      查出每个分表在该限制条件下的
 *          max(查询条件) as extreme
 *          count(*) as cnt
 *      汇总 数量 和 最小极值
 *      查出每个分表 < extreme 的数量 和 = extreme 的数量，记为：countLt, countEq, countLte
 *      其中：countLt一定 < left
 *      分类讨论：
 *          如果 countLte < left，说明要查的数据都是 > extreme的
 *              更新区域范围 [left - countLte, right - countLte]，跳转步骤2
 *          如果 left <= countLte <= right，说明要查的数据被 extreme 截成两段
 *              = extreme 的，一共有 countLte - left + 1 个，记为：countLeft。且countLeft是等于extreme的末尾数据
 *                  所有分表反序取countLeft个，汇总取前countLeft个，结果再反序
 *              > extreme 的，一共有 right - countLte 个，记为：countRight。且countRight是大于extreme的头部数据
 *                  所有分表取且countRight个，汇总取前且countRight个
 *              汇总结果返回
 *          如果 countLte > right，说明要查的数据都是 = extreme的
 *              如果存在第二个排序字段，固化第一个排序字段，按照第二个排序字段进入第二步
 *              否则按照 = extreme的查询条件，按表名字典序汇总
 *                  更新区域 [left - countLt, right - countLt]，固化查询条件 = extreme，按字典序取区域中的数据
 *
 *  @author ala
 *  @date 2025-03-03 22:27
 */
public class ShardPagerWithOrderByHandler {


    /**
     *  执行器
     */
    protected ShardPagerParameterExecutor executor = new ShardPagerParameterExecutor();


    /**
     *  含orderBy的分页
     */
    public Map<ShardInfo, ShardExecutor.Result> doPageWithOrderBy(ShardPagerParameter parameter) {
        //  取Pager参数，排序参数，包装成本次查询对象
        Pager pager = parameter.getPager();
        AbstractOrderBy orderBy = parameter.getOrderBy();

        //  查总数
        ShardCountDO totalDO = executor.doPagerTotal(parameter);
        parameter.total(totalDO.getTotal()).currentCountDO(totalDO);

        return Maps.build(KV.build(ShardInfo.EMPTY, ShardExecutor.Result.success(ShardInfo.EMPTY, doPage(parameter))));
    }


    /**
     *  执行常规分页
     */
    protected Pager doPage(ShardPagerParameter parameter) {
        Pager pager = parameter.getPager();
        AbstractOrderBy orderBy = parameter.getOrderBy();
        orderBy.setItemIdx(parameter.getOrderIndex());

        //  如果只剩1个分库分表，按照当前的page直接查
        if (parameter.getDataMethod().getArgs().size() == 1) {
            return Pager.build(pager.getNo(), pager.getSize()).total(parameter.getTotal()).datas(executor.doPagerOnlyOnePage(parameter));
        }

        //  如果本次查询的左界在total外面，直接返回空数据
        if (parameter.getLeft() > parameter.getCurrentCountDO().getTotal()) {
            return Pager.build(pager.getNo(), pager.getSize()).total(parameter.getTotal()).datas(new ArrayList());
        }

        //  如果left左边已经不足1个pageSize，或不足shardSize。则判定已经到了区域附近，直接获取
        int offset = parameter.getLeft() - 1;
        if (offset < Math.max(pager.getSize(), parameter.getDataMethod().getArgs().size())) {
            List datas = executor.doPagerList(parameter, parameter.getLeft(), parameter.getRight());
            return Pager.build(pager.getNo(), pager.getSize()).total(parameter.getTotal()).datas(datas);
        }

        //  假设数据搜均衡的，把offset均摊到每个分表，求每个分表的数据量和极值
        int limit = offset / parameter.getDataMethod().getArgs().size();
        //  特殊的：如果每个分表分得的数据不足一页，直接查（跳步不足1个pageSize时，直接查出来）
        if (limit < pager.getSize()) {
            List datas = executor.doPagerList(parameter, parameter.getLeft(), parameter.getRight());
            return Pager.build(pager.getNo(), pager.getSize()).total(parameter.getTotal()).datas(datas);
        }

        ShardPageCountDO pc = executor.doLimitCount(parameter, limit);
        //  取每个分表 极值左边 的数量和 等于极值 的数量，并汇总 countLeft, countEq, countLeftEq
        ShardPageExtremeDO pe = executor.doExtremeCount(parameter, pc.getExtreme());

        //  如果 countLeftEq < left，说明要查的数据是在extreme的右边，固化extreme查询条件，更新left right，递归进入
        if (pe.getCountLeftEq() < parameter.getLeft()) {
            return doPageLt(parameter, pc, pe);
        }
        //  如果 left <= countLeftEq <= right，说明要查的数据被 extreme 截成两段
        else if (parameter.getLeft() <= pe.getCountLeftEq() && pe.getCountLeftEq() <= parameter.getRight()) {
            return doPageBetween(parameter, pc, pe);
        }
        //  如果 countLeftEq > right，说明要查的数据都等于 extreme
        else {
            return doPageGt(parameter, pc, pe);
        }
    }
    /**
     *  如果 countLeftEq < left，说明要查的数据是在extreme的右边，固化extreme查询条件，更新left right，递归进入
     */
    protected Pager doPageLt(ShardPagerParameter parameter, ShardPageCountDO pc, ShardPageExtremeDO pe) {
        AbstractOrderBy orderBy = parameter.getOrderBy();
        orderBy.appendStationary(orderBy.getCurrentItem().copy(), pc.getExtreme());
        //  区域往右推
        moveRight(parameter, pe.getCountLeftEq(), (shardInfo -> pe.getShards().get(shardInfo).getCountLeftEq()));
        return doPage(parameter);
    }
    /**
     *  如果 left <= countLeftEq <= right，说明要查的数据被 extreme 截成两段
     *  <p> 分别查询 [left, countLeftEq] 部分和 (countLeftEq, right] 部分
     */
    protected Pager doPageBetween(ShardPagerParameter parameter, ShardPageCountDO pc, ShardPageExtremeDO pe) {
        Pager pager = parameter.getPager();
        List list = executor.doPagerLeftRight(parameter, parameter.getLeft(), pe.getCountLeftEq(), parameter.getRight(), pc.getExtreme());
        return Pager.build(pager.getNo(), pager.getSize()).total(parameter.getTotal()).datas(list);
    }
    /**
     *  如果 countLeftEq > right，说明要查的数据都等于 extreme
     *  <p> 如果orderBy还有其他分页字段，用下一个分页字段继续递归
     *  <p> 如果已经没有了，固化极值左边的查询条件，按表名的字母序取数
     */
    protected Pager doPageGt(ShardPagerParameter parameter, ShardPageCountDO pc, ShardPageExtremeDO pe) {
        Pager pager = parameter.getPager();
        AbstractOrderBy orderBy = parameter.getOrderBy();

        //  判断是否还有其他排序字段，如果有，固化当前排序字段继续递归
        if (parameter.getOrderIndex() < orderBy.getItems().size() - 1) {
            orderBy.appendStationary(orderBy.getCurrentItem().copy().eq(true), pc.getExtreme());
            parameter.setOrderIndex(parameter.getOrderIndex() + 1);
            //  区域往右推
            moveRight(parameter, pe.getCountLeft(), (shardInfo -> pe.getShards().get(shardInfo).getCountLeft()));
            return doPage(parameter);
        }
        //  如果没有了，固化当前排序字段的值按eq查询。结果按表名字母序取[left - countLeft, right - countLeft]
        else {
            return Pager.build(pager.getNo(), pager.getSize()).total(parameter.getTotal()).datas(executor.doPagerNoOrder(parameter, pc, pe));
        }
    }


    /**
     *  区域往右推
     */
    protected void moveRight(ShardPagerParameter parameter, int left, Function<ShardInfo, Integer> lefts) {
        //  区域往前推
        parameter.setLeft(parameter.getLeft() - left);
        parameter.setRight(parameter.getRight() - left);
        //  总量和分量往前推，同时删除分量为0的分表
        parameter.getCurrentCountDO().setTotal(parameter.getTotal() - left);
        List<ShardInfo> emptys = new ArrayList<>();
        parameter.getCurrentCountDO().getShards().keySet().forEach(shardInfo -> {
            int c = parameter.getCurrentCountDO().getShards().merge(shardInfo, -lefts.apply(shardInfo), Integer::sum);
            if (c == 0) {
                emptys.add(shardInfo);
            }
        });
        if (!emptys.isEmpty()) {
            emptys.forEach(shardInfo -> {
                parameter.getCurrentCountDO().getShards().remove(shardInfo);
                parameter.getCountMethod().getArgs().remove(shardInfo);
                parameter.getDataMethod().getArgs().remove(shardInfo);
            });
        }
    }
}
